package com.zxhysy.log.annotation.advisor;

import com.zxhysy.log.annotation.entity.LogRecord;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.MethodClassKey;
import org.springframework.lang.NonNull;
import org.springframework.util.ClassUtils;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 日志方法注解属性容器，加了缓存
 * @author zhangxinhong
 * @since 2021/9/22 12:15
 */
public class LogSource implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 方法缓存集合
     */
    private final Map<Object, Collection<LogRecord>> attributeCache = new ConcurrentHashMap<>(1024);

    /**
     * public 方法才可以
     */
    private final boolean publicMethodsOnly;

    /**
     * 须拦截的包
     */
    private final String[] tenant;

    /**
     * 注释解析器
     */
    private final LogAnnotationParser logAnnotationParser;

    public LogSource(boolean publicMethodsOnly, String[] tenant) {
        this.publicMethodsOnly = publicMethodsOnly;
        this.tenant = tenant;
        this.logAnnotationParser = new LogAnnotationParser();
    }

    /**
     * 判断是否需要过滤
     * @param targetClass 目标类
     * @return true/false
     */
    public boolean isCandidateClass(Class<?> targetClass) {
        for (String basePacket : tenant) {
            if (targetClass.getName().startsWith(basePacket)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取日志注解
     * @param method      目标方法
     * @param targetClass 目标类
     * @return 解析结果
     */
    @NonNull
    public Collection<LogRecord> getLogOperations(Method method, Class<?> targetClass) {
        // 源类 没必要
        if (method.getDeclaringClass() == Object.class) {
            return Collections.emptyList();
        }

        // 从缓存中先获取一份
        return this.attributeCache.computeIfAbsent(new MethodClassKey(method, targetClass), k -> computeLogRecord(method, targetClass));
    }

    /**
     * 解析出log注解属性
     * @param method      目标方法
     * @param targetClass 目标类
     * @return 计算结果集合
     */
    @NonNull
    private Collection<LogRecord> computeLogRecord(Method method, Class<?> targetClass) {
        // 方法是否必须是 public 才可以
        if (this.publicMethodsOnly && !Modifier.isPublic(method.getModifiers())) {
            return Collections.emptyList();
        }

        // 方法可能是一个接口, 但是我们需要目标类的属性
        // 如果目标类为空 则返回的还是原来的方法
        // 这里获取实现类接口
        Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

        // 首先尝试方法再目标类上有注解
        Collection<LogRecord> opDef = logAnnotationParser.parseLogAnnotations(specificMethod);
        if (!opDef.isEmpty()) {
            return opDef;
        }

        // 再次尝试直接从目标类解析
        opDef = logAnnotationParser.parseLogAnnotations(specificMethod.getDeclaringClass());
        if (!opDef.isEmpty() && ClassUtils.isUserLevelMethod(method)) {
            return opDef;
        }

        // 如果还为空 就解析原方法
        if (specificMethod != method) {
            // 退一步是查看原始方法，如果原始方法有就用原始方法的
            opDef = logAnnotationParser.parseLogAnnotations(method);
            if (!opDef.isEmpty()) {
                return opDef;
            }
            // 最后一个回退是原始方法的类。
            opDef = logAnnotationParser.parseLogAnnotations(method.getDeclaringClass());
            if (!opDef.isEmpty() && ClassUtils.isUserLevelMethod(method)) {
                return opDef;
            }
        }
        return opDef;
    }

}
